home *** CD-ROM | disk | FTP | other *** search
/ PC World Komputer 2010 April / PCWorld0410.iso / hity wydania / Ubuntu 9.10 PL / karmelkowy-koliberek-desktop-9.10-i386-PL.iso / casper / filesystem.squashfs / usr / lib / xulrunner-1.9.1.5 / modules / SpatialNavigation.js < prev    next >
Text File  |  2009-11-08  |  16KB  |  559 lines

  1. /* ***** BEGIN LICENSE BLOCK *****
  2.  * Version: MPL 1.1/GPL 2.0/LGPL 2.1
  3.  *
  4.  * The contents of this file are subject to the Mozilla Public License Version
  5.  * 1.1 (the "License"); you may not use this file except in compliance with
  6.  * the License. You may obtain a copy of the License at
  7.  * http://www.mozilla.org/MPL/
  8.  *
  9.  * Software distributed under the License is distributed on an "AS IS" basis,
  10.  * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
  11.  * for the specific language governing rights and limitations under the
  12.  * License.
  13.  *
  14.  * The Original Code is Spatial Navigation.
  15.  *
  16.  * The Initial Developer of the Original Code is Mozilla Corporation
  17.  * Portions created by the Initial Developer are Copyright (C) 2008
  18.  * the Initial Developer. All Rights Reserved.
  19.  *
  20.  * Contributor(s):
  21.  *  Doug Turner <dougt@meer.net>  (Original Author)
  22.  *
  23.  * Alternatively, the contents of this file may be used under the terms of
  24.  * either the GNU General Public License Version 2 or later (the "GPL"), or
  25.  * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
  26.  * in which case the provisions of the GPL or the LGPL are applicable instead
  27.  * of those above. If you wish to allow use of your version of this file only
  28.  * under the terms of either the GPL or the LGPL, and not to allow others to
  29.  * use your version of this file under the terms of the MPL, indicate your
  30.  * decision by deleting the provisions above and replace them with the notice
  31.  * and other provisions required by the GPL or the LGPL. If you do not delete
  32.  * the provisions above, a recipient may use your version of this file under
  33.  * the terms of any one of the MPL, the GPL or the LGPL.
  34.  *
  35.  * ***** END LICENSE BLOCK ***** */
  36.  
  37. /**
  38.  * 
  39.  * Import this module through
  40.  *
  41.  * Components.utils.import("resource://gre/modules/SpatialNavigation.js");
  42.  *
  43.  * Usage: (Literal class)
  44.  *
  45.  * SpatialNavigation(browser_element, optional_callback);
  46.  *
  47.  * optional_callback will be called when a new element is focused.
  48.  *
  49.  *    function optional_callback(element) {}
  50.  *
  51.  */
  52.  
  53.  
  54. var EXPORTED_SYMBOLS = ["SpatialNavigation"];
  55.  
  56. var SpatialNavigation = {
  57.  
  58.   init: function(browser, callback) {
  59.     browser.addEventListener("keypress", function (event) { _onInputKeyPress(event, callback) }, true);
  60.   },
  61.   
  62.   uninit: function() {
  63.   }
  64. };
  65.  
  66.  
  67. // Private stuff
  68.  
  69. const Cc = Components.classes;
  70. const Ci = Components.interfaces;
  71.  
  72. function dump(msg)
  73. {
  74.   var console = Cc["@mozilla.org/consoleservice;1"].getService(Ci.nsIConsoleService);
  75.   console.logStringMessage("*** SNAV: " + msg);
  76. }
  77.  
  78. var gDirectionalBias = 10;
  79. var gRectFudge = 1;
  80.  
  81. // modifier values
  82. const kAlt   = "alt";
  83. const kShift = "shift";
  84. const kCtrl  = "ctrl";
  85. const kNone  = "none";
  86.  
  87. function _onInputKeyPress (event, callback) {
  88.  
  89.   // Use whatever key value is available (either keyCode or charCode).
  90.   // It might be useful for addons or whoever wants to set different
  91.   // key to be used here (e.g. "a", "F1", "arrowUp", ...).
  92.   var key = event.which || event.keyCode;
  93.  
  94.   // If it isn't enabled, bail.
  95.   if (!PrefObserver['enabled'])
  96.     return;
  97.  
  98.   if (key != PrefObserver['keyCodeDown']  &&
  99.       key != PrefObserver['keyCodeRight'] &&
  100.       key != PrefObserver['keyCodeUp'] &&
  101.       key != PrefObserver['keyCodeLeft'])
  102.     return;
  103.  
  104.   // If it is not using the modifiers it should, bail.
  105.   if (!event.altKey && PrefObserver['modifierAlt'])
  106.     return;
  107.  
  108.   if (!event.shiftKey && PrefObserver['modifierShift'])
  109.     return;
  110.  
  111.   if (!event.crtlKey && PrefObserver['modifierCtrl'])
  112.     return;
  113.  
  114.   var target = event.target;
  115.  
  116.   var doc = target.ownerDocument;
  117.  
  118.   // If it is XUL content (e.g. about:config), bail.
  119.   if (!PrefObserver['xulContentEnabled'] && doc instanceof Ci.nsIDOMXULDocument)
  120.     return ;
  121.  
  122.   // check to see if we are in a textarea or text input element, and if so,
  123.   // ensure that we let the arrow keys work properly.
  124.   if (target instanceof Ci.nsIDOMHTMLHtmlElement) {
  125.       _focusNextUsingCmdDispatcher(key, callback);
  126.       return;
  127.   }
  128.  
  129.   if ((target instanceof Ci.nsIDOMHTMLInputElement && (target.type == "text" || target.type == "password")) ||
  130.       target instanceof Ci.nsIDOMHTMLTextAreaElement ) {
  131.     
  132.     // if there is any selection at all, just ignore
  133.     if (target.selectionEnd - target.selectionStart > 0)
  134.       return;
  135.     
  136.     // if there is no text, there is nothing special to do.
  137.     if (target.textLength > 0) {
  138.       if (key == PrefObserver['keyCodeRight'] ||
  139.           key == PrefObserver['keyCodeDown'] ) {
  140.         // we are moving forward into the document
  141.         if (target.textLength != target.selectionEnd)
  142.           return;
  143.       }
  144.       else
  145.       {
  146.         // we are at the start of the text, okay to move 
  147.         if (target.selectionStart != 0)
  148.           return;
  149.       }
  150.     }
  151.   }
  152.  
  153.   // Check to see if we are in a select
  154.   if (target instanceof Ci.nsIDOMHTMLSelectElement)
  155.   {
  156.     if (key == PrefObserver['keyCodeDown']) {
  157.       if (target.selectedIndex + 1 < target.length)
  158.         return;
  159.     }
  160.  
  161.     if (key == PrefObserver['keyCodeUp']) {
  162.       if (target.selectedIndex > 0)
  163.         return;
  164.     }
  165.   }
  166.  
  167.   function snavfilter(node) {
  168.  
  169.     if (node instanceof Ci.nsIDOMHTMLLinkElement ||
  170.         node instanceof Ci.nsIDOMHTMLAnchorElement) {
  171.       // if a anchor doesn't have a href, don't target it.
  172.       if (node.href == "")
  173.         return Ci.nsIDOMNodeFilter.FILTER_SKIP;
  174.       return  Ci.nsIDOMNodeFilter.FILTER_ACCEPT;
  175.     }
  176.     
  177.     if ((node instanceof Ci.nsIDOMHTMLButtonElement ||
  178.          node instanceof Ci.nsIDOMHTMLInputElement ||
  179.          node instanceof Ci.nsIDOMHTMLLinkElement ||
  180.          node instanceof Ci.nsIDOMHTMLOptGroupElement ||
  181.          node instanceof Ci.nsIDOMHTMLSelectElement ||
  182.          node instanceof Ci.nsIDOMHTMLTextAreaElement) &&
  183.         node.disabled == false)
  184.       return Ci.nsIDOMNodeFilter.FILTER_ACCEPT;
  185.     
  186.     return Ci.nsIDOMNodeFilter.FILTER_SKIP;
  187.   }
  188.  
  189.   var bestElementToFocus = null;
  190.   var distanceToBestElement = Infinity;
  191.   var focusedRect = _inflateRect(target.getBoundingClientRect(),
  192.                                  - gRectFudge);
  193.  
  194.   var treeWalker = doc.createTreeWalker(doc, Ci.nsIDOMNodeFilter.SHOW_ELEMENT, snavfilter, false);
  195.   var nextNode;
  196.   
  197.   while ((nextNode = treeWalker.nextNode())) {
  198.  
  199.     if (nextNode == target)
  200.       continue;
  201.  
  202.     var nextRect = _inflateRect(nextNode.getBoundingClientRect(),
  203.                                 - gRectFudge);
  204.  
  205.     if (! _isRectInDirection(key, focusedRect, nextRect))
  206.       continue;
  207.  
  208.     var distance = _spatialDistance(key, focusedRect, nextRect);
  209.  
  210.     //dump("looking at: " + nextNode + " " + distance);
  211.     
  212.     if (distance <= distanceToBestElement && distance > 0) {
  213.       distanceToBestElement = distance;
  214.       bestElementToFocus = nextNode;
  215.     }
  216.   }
  217.   
  218.   if (bestElementToFocus != null) {
  219.     //dump("focusing element  " + bestElementToFocus.nodeName + " " + bestElementToFocus) + "id=" + bestElementToFocus.getAttribute("id");
  220.  
  221.     // Wishing we could do element.focus()
  222.     doc.defaultView.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindowUtils).focus(bestElementToFocus);
  223.  
  224.     // if it is a text element, select all.
  225.     if((bestElementToFocus instanceof Ci.nsIDOMHTMLInputElement && (bestElementToFocus.type == "text" || bestElementToFocus.type == "password")) ||
  226.        bestElementToFocus instanceof Ci.nsIDOMHTMLTextAreaElement ) {
  227.       bestElementToFocus.selectionStart = 0;
  228.       bestElementToFocus.selectionEnd = bestElementToFocus.textLength;
  229.     }
  230.  
  231.     if (callback != undefined)
  232.       callback(bestElementToFocus);
  233.     
  234.   } else {
  235.     // couldn't find anything.  just advance and hope.
  236.     _focusNextUsingCmdDispatcher(key, callback);
  237.   }
  238.  
  239.   event.preventDefault();
  240.   event.stopPropagation();
  241. }
  242.  
  243. function _focusNextUsingCmdDispatcher(key, callback) {
  244.  
  245.     var windowMediator = Cc['@mozilla.org/appshell/window-mediator;1'].getService(Ci.nsIWindowMediator);
  246.     var window = windowMediator.getMostRecentWindow("navigator:browser");
  247.  
  248.     if (key == PrefObserver['keyCodeRight'] || key == PrefObserver['keyCodeDown']) {
  249.       window.document.commandDispatcher.advanceFocus();
  250.     } else {
  251.       window.document.commandDispatcher.rewindFocus();
  252.     }
  253.  
  254.     if (callback != undefined)
  255.       callback(null);
  256. }
  257.  
  258. function _isRectInDirection(key, focusedRect, anotherRect)
  259. {
  260.   if (key == PrefObserver['keyCodeLeft']) {
  261.     return (anotherRect.left < focusedRect.left);
  262.   }
  263.  
  264.   if (key == PrefObserver['keyCodeRight']) {
  265.     return (anotherRect.right > focusedRect.right);
  266.   }
  267.  
  268.   if (key == PrefObserver['keyCodeUp']) {
  269.     return (anotherRect.top < focusedRect.top);
  270.   }
  271.  
  272.   if (key == PrefObserver['keyCodeDown']) {
  273.     return (anotherRect.bottom > focusedRect.bottom);
  274.   }
  275.     return false;
  276. }
  277.  
  278. function _inflateRect(rect, value)
  279. {
  280.   var newRect = new Object();
  281.   
  282.   newRect.left   = rect.left - value;
  283.   newRect.top    = rect.top - value;
  284.   newRect.right  = rect.right  + value;
  285.   newRect.bottom = rect.bottom + value;
  286.   return newRect;
  287. }
  288.  
  289. function _containsRect(a, b)
  290. {
  291.   return ( (b.left  <= a.right) &&
  292.            (b.right >= a.left)  &&
  293.            (b.top  <= a.bottom) &&
  294.            (b.bottom >= a.top) );
  295. }
  296.  
  297. function _spatialDistance(key, a, b)
  298. {
  299.   var inlineNavigation = false;
  300.   var mx, my, nx, ny;
  301.  
  302.   if (key == PrefObserver['keyCodeLeft']) {
  303.  
  304.     //  |---|
  305.     //  |---|
  306.     //
  307.     //  |---|  |---|
  308.     //  |---|  |---|
  309.     //
  310.     //  |---|
  311.     //  |---|
  312.     //
  313.     
  314.     if (a.top > b.bottom) {
  315.       // the b rect is above a.
  316.       mx = a.left;
  317.       my = a.top;
  318.       nx = b.right;
  319.       ny = b.bottom;
  320.     }
  321.     else if (a.bottom < b.top) {
  322.       // the b rect is below a.
  323.       mx = a.left;
  324.       my = a.bottom;
  325.       nx = b.right;
  326.       ny = b.top;       
  327.     }
  328.     else {
  329.       mx = a.left;
  330.       my = 0;
  331.       nx = b.right;
  332.       ny = 0;
  333.     }
  334.   } else if (key == PrefObserver['keyCodeRight']) {
  335.  
  336.     //         |---|
  337.     //         |---|
  338.     //
  339.     //  |---|  |---|
  340.     //  |---|  |---|
  341.     //
  342.     //         |---|
  343.     //         |---|
  344.     //
  345.     
  346.     if (a.top > b.bottom) {
  347.       // the b rect is above a.
  348.       mx = a.right;
  349.       my = a.top;
  350.       nx = b.left;
  351.       ny = b.bottom;
  352.     }
  353.     else if (a.bottom < b.top) {
  354.       // the b rect is below a.
  355.       mx = a.right;
  356.       my = a.bottom;
  357.       nx = b.left;
  358.       ny = b.top;       
  359.     } else {
  360.       mx = a.right;
  361.       my = 0;
  362.       nx = b.left;
  363.       ny = 0;
  364.     }
  365.   } else if (key == PrefObserver['keyCodeUp']) {
  366.  
  367.     //  |---|  |---|  |---|
  368.     //  |---|  |---|  |---|
  369.     //
  370.     //         |---|
  371.     //         |---|
  372.     //
  373.     
  374.     if (a.left > b.right) {
  375.       // the b rect is to the left of a.
  376.       mx = a.left;
  377.       my = a.top;
  378.       nx = b.right;
  379.       ny = b.bottom;
  380.     } else if (a.right < b.left) {
  381.       // the b rect is to the right of a
  382.       mx = a.right;
  383.       my = a.top;
  384.       nx = b.left;
  385.       ny = b.bottom;       
  386.     } else {
  387.       // both b and a share some common x's.
  388.       mx = 0;
  389.       my = a.top;
  390.       nx = 0;
  391.       ny = b.bottom;
  392.     }
  393.   } else if (key == PrefObserver['keyCodeDown']) {
  394.  
  395.     //         |---|
  396.     //         |---|
  397.     //
  398.     //  |---|  |---|  |---|
  399.     //  |---|  |---|  |---|
  400.     //
  401.     
  402.     if (a.left > b.right) {
  403.       // the b rect is to the left of a.
  404.       mx = a.left;
  405.       my = a.bottom;
  406.       nx = b.right;
  407.       ny = b.top;
  408.     } else if (a.right < b.left) {
  409.       // the b rect is to the right of a
  410.       mx = a.right;
  411.       my = a.bottom;
  412.       nx = b.left;
  413.       ny = b.top;      
  414.     } else {
  415.       // both b and a share some common x's.
  416.       mx = 0;
  417.       my = a.bottom;
  418.       nx = 0;
  419.       ny = b.top;
  420.     }
  421.   }
  422.   
  423.   var scopedRect = _inflateRect(a, gRectFudge);
  424.  
  425.   if (key == PrefObserver['keyCodeLeft'] ||
  426.       key == PrefObserver['keyCodeRight']) {
  427.     scopedRect.left = 0;
  428.     scopedRect.right = Infinity;
  429.     inlineNavigation = _containsRect(scopedRect, b);
  430.   }
  431.   else if (key == PrefObserver['keyCodeUp'] ||
  432.            key == PrefObserver['keyCodeDown']) {
  433.     scopedRect.top = 0;
  434.     scopedRect.bottom = Infinity;
  435.     inlineNavigation = _containsRect(scopedRect, b);
  436.   }
  437.   
  438.   var d = Math.pow((mx-nx), 2) + Math.pow((my-ny), 2);
  439.   
  440.   // prefer elements directly aligned with the focused element
  441.   if (inlineNavigation)
  442.     d /= gDirectionalBias;
  443.   
  444.   return d;
  445. }
  446.  
  447. // Snav preference observer
  448.  
  449. PrefObserver = {
  450.  
  451.   register: function()
  452.   {
  453.     this.prefService = Cc["@mozilla.org/preferences-service;1"]
  454.                        .getService(Ci.nsIPrefService);
  455.  
  456.     this._branch = this.prefService.getBranch("snav.");
  457.     this._branch.QueryInterface(Ci.nsIPrefBranch2);
  458.     this._branch.addObserver("", this, false);
  459.  
  460.     // set current or default pref values
  461.     this.observe(null, "nsPref:changed", "enabled");
  462.     this.observe(null, "nsPref:changed", "xulContentEnabled");
  463.     this.observe(null, "nsPref:changed", "keyCode.modifier");
  464.     this.observe(null, "nsPref:changed", "keyCode.right");
  465.     this.observe(null, "nsPref:changed", "keyCode.up");
  466.     this.observe(null, "nsPref:changed", "keyCode.down");
  467.     this.observe(null, "nsPref:changed", "keyCode.left");
  468.   },
  469.  
  470.   observe: function(aSubject, aTopic, aData)
  471.   {
  472.     if(aTopic != "nsPref:changed")
  473.       return;
  474.  
  475.     // aSubject is the nsIPrefBranch we're observing (after appropriate QI)
  476.     // aData is the name of the pref that's been changed (relative to aSubject)
  477.     switch (aData) {
  478.       case "enabled":
  479.         try {
  480.           this.enabled = this._branch.getBoolPref("enabled");
  481.         } catch(e) {
  482.           this.enabled = false;
  483.         }
  484.         break;
  485.       case "xulContentEnabled":
  486.         try {
  487.           this.xulContentEnabled = this._branch.getBoolPref("xulContentEnabled");
  488.         } catch(e) {
  489.           this.xulContentEnabled = false;
  490.         }
  491.         break;
  492.  
  493.       case "keyCode.modifier":
  494.         try {
  495.           this.keyCodeModifier = this._branch.getCharPref("keyCode.modifier");
  496.  
  497.           // resetting modifiers
  498.           this.modifierAlt = false;
  499.           this.modifierShift = false;
  500.           this.modifierCtrl = false;
  501.  
  502.           if (this.keyCodeModifier != this.kNone)
  503.           {
  504.             // use are using '+' as a separator in about:config.
  505.             var mods = this.keyCodeModifier.split(/\++/);
  506.             for (var i = 0; i < mods.length; i++) {
  507.               var mod = mods[i].toLowerCase();
  508.               if (mod == "")
  509.                 continue;
  510.               else if (mod == kAlt)
  511.                 this.modifierAlt = true;
  512.               else if (mod == kShift)
  513.                 this.modifierShift = true;
  514.               else if (mod == kCtrl)
  515.                 this.modifierCtrl = true;
  516.               else {
  517.                 this.keyCodeModifier = kNone;
  518.                 break;
  519.               }
  520.             }
  521.           }
  522.         } catch(e) {
  523.             this.keyCodeModifier = kNone;
  524.         }
  525.         break;
  526.       case "keyCode.up":
  527.         try {
  528.           this.keyCodeUp = this._branch.getIntPref("keyCode.up");
  529.         } catch(e) {
  530.           this.keyCodeUp = Ci.nsIDOMKeyEvent.DOM_VK_UP;
  531.         }
  532.         break;
  533.       case "keyCode.down":
  534.         try {
  535.           this.keyCodeDown = this._branch.getIntPref("keyCode.down");
  536.         } catch(e) {
  537.           this.keyCodeDown = Ci.nsIDOMKeyEvent.DOM_VK_DOWN;
  538.         }
  539.         break;
  540.       case "keyCode.left":
  541.         try {
  542.           this.keyCodeLeft = this._branch.getIntPref("keyCode.left");
  543.         } catch(e) {
  544.           this.keyCodeLeft = Ci.nsIDOMKeyEvent.DOM_VK_LEFT;
  545.         }
  546.         break;
  547.       case "keyCode.right":
  548.         try {
  549.           this.keyCodeRight = this._branch.getIntPref("keyCode.right");
  550.         } catch(e) {
  551.           this.keyCodeRight = Ci.nsIDOMKeyEvent.DOM_VK_RIGHT;
  552.         }
  553.         break;
  554.     }
  555.   },
  556. }
  557.  
  558. PrefObserver.register();
  559.